高DPI下控件位置错乱问题简单粗暴的解决方法

近日写一软件,遇到了高DPI下界面错乱的问题,在网上搜索了好几天,都没有满意的解决方法。也下载了一些坛友的解决方案示例,其基本思路是按比例将高DPI下控件的位置及大小恢复为默认DPI下的位置及大小,经实验,这种方法对简单界面是有效的,当界面比较复杂,控件比较多时,仍会错乱。

反复对比计算不同DPI下的控件大小及位置,发现实在是摸不透WINDOWS对高DPI下的控件是如何调整其位置及大小的,完全没有固定的比例,所以坛友的解决方案只能将部件控件的位置予以恢复,大小比原来小了许多,另一些则位置也不正确。

在其它论坛及博客等也查看了些类似的文章,始终是无法解决。感觉有个网友说的很正确:说能完全解决高DPI界面错乱问题的都是牛鬼蛇神!

问题总得解决,思路还是让高DPI下控件恢复到默认DPI时位置及大小,即然无法按比例调整,何不我就记录下控件原有位置!

说干就干。第一次,在窗口初始化完成后,我调用一个函数,枚举所有子窗口,记录下其位置,生成一个表格,然后保存到文件中,之后再把这个文件加入软件。以后每次启动软件,就按这个表格调整窗口所有控件的大小及位置。界面终于不再错乱了。

至于字体,按比例调整是没有问题的,所以字体信息无需记录。

当然这个解决方案不是完美的,因为控件大小及字体一直是默认DPI下的大小,所以在高DPI下显得与整个桌面不协调,就是比桌面上其它软件的字体要小。这个要解决也行,就是把所有控件按自定的比例缩放。不过我嫌麻烦,没去做。

其次就是,由系统设置的一些窗口没有调整,比如标题框、弹出的对话框、右键菜单等,所以在你的软件上会有些字大,有些字小。

但至少不错乱了!

附枚举窗口及调整字体的代码:

#ifdef DEBUG_WINDOW_LIST
//控件列表
typedef struct structWindowItem
{
    UINT  CtrlId;
    CHAR  szClassName[STRING_SPACE];  
    RECT  Location;
}WNDITEM;
typedef struct structWindowList
{
    HWND     ParentHwnd;
    UINT     Count;  
    WNDITEM  Item[WINDOWS_MAX];  
}WNDLIST;
WNDLIST    strWindowList;
#define STRING_SPACE  256
// 枚举子窗口
int ChildWindowList(HWND hwnd)
{
    strWindowList.ParentHwnd = hwnd;
    strWindowList.Count = 0;  
    memset(strWindowList.Item, 0, sizeof(strWindowList.Item));
    
    ::EnumChildWindows(hwnd, ChildWindowProcess, NULL);
    //将子窗口参数保存到文件
#if 0
    CStdioFile cFileList;
    if(cFileList.Open(_T("window.list"), CFile::modeCreate|CFile::modeWrite|CFile::shareDenyNone|CFile::typeText))
    {
        int nLength;
        char szBuffer[STRING_SPACE];
        char szClassName[STRING_SPACE];
        cFileList.WriteString(_T("const WNDITEM WindowsList[] = \n{\n"));
        for(UINT nIndex = 0; nIndex < strWindowList.Count; nIndex++)
        {
            memset(szBuffer, 0, sizeof(szBuffer));
            memset(szClassName, 0x20, sizeof(szBuffer));
            nLength = strlen(strWindowList.Item[nIndex].szClassName);
            szClassName[0] = _T('\"');
            memcpy(szClassName + 1, strWindowList.Item[nIndex].szClassName, nLength);
            szClassName[nLength + 1] = _T('\"');
            szClassName[nLength + 2] = _T(',');
            szClassName[15] = 0;
            sprintf_s(szBuffer, _T("  {%4d, %s {%4d, %4d, %4d, %4d}},\n"), 
                strWindowList.Item[nIndex].CtrlId,
                szClassName,
                strWindowList.Item[nIndex].Location.left,
                strWindowList.Item[nIndex].Location.top,
                strWindowList.Item[nIndex].Location.right,
                strWindowList.Item[nIndex].Location.bottom);
            cFileList.WriteString(szBuffer);
        }
        
        cFileList.WriteString(_T("};\n"));
        cFileList.Close();
    }
#endif
    return strWindowList.Count;
}
// 枚举子窗口调用
BOOL CALLBACK ChildWindowProcess(HWND hwnd, LPARAM lParam)
{
    int   nIndex;
    if((hwnd != NULL) && (strWindowList.Count < WINDOWS_MAX))
    {
        nIndex = strWindowList.Count;
        //ID
        strWindowList.Item[nIndex].CtrlId = ::GetDlgCtrlID(hwnd);
        //类名
        ::GetClassName(hwnd, strWindowList.Item[nIndex].szClassName, STRING_SPACE);
        //位置
        ::GetWindowRect(hwnd, &strWindowList.Item[nIndex].Location);
        //转换为窗口内坐标
        CWnd *pWnd = CWnd::FromHandle(strWindowList.ParentHwnd);
        pWnd->ScreenToClient(&strWindowList.Item[nIndex].Location);
        strWindowList.Count++;
    }
    return TRUE;
}
#endif
//调整字体
#define DEFAULT_DPI    96.0
BOOL ChildWindowFontRestore(HWND hChildWnd )
{
    LOGFONT  LgFont;
    int DpiY;
    HDC hDC = ::GetDC(NULL);
    if(hDC != NULL)
    {
        DpiY= GetDeviceCaps(hDC, LOGPIXELSY);
        ::ReleaseDC(NULL, hDC);
    }
    double  dbScale = (double)DEFAULT_DPI /  DpiY; 
    //获取当前字体
    HFONT  hFont = (HFONT)::SendMessage(hChildWnd, WM_GETFONT, NULL, NULL);
    if(hFont != NULL)
    {
        //获取字体信息
        ::GetObject(hFont, sizeof(LOGFONT), &LgFont);
        //按比例修改大小
        LgFont.lfHeight = LONG( (double)LgFont.lfHeight * dbScale + 0.5f);
        //生成物新的字体
        hFont = ::CreateFontIndirect(&LgFont);
        if(hFont != NULL)
        {   
            //重设字体
            SendMessage(hChildWnd, WM_SETFONT, (LPARAM)hFont, TRUE);
        }
    }   
    return TRUE;
}

HFONT的保存及释放过程我没有写出来,实际应用中建立的字体句柄要保存,在程序结束时释放。

还有就是,若程序是在WIN8以上系统下运行,这样调的结果可能反倒导致界面变乱,貌似WIN8以上系统已解决了这个问题,所以在软件里应加上系统检测,在WIN8以上就交由系统去处理。

本页共127段,4448个字符,6309 Byte(字节)